/***********************************************************************************
**  
* @copyright (c) 2010-2019,  Technology Co., LTD. All Right Reserved.
*
************************************************************************************/
/**
* @file	    duye_file_monitor.cpp
* @version     
* @brief      
* @author
* @date	    2013-03-30
* @note 
*
* 1. 2013-03-30 Created this file
*/
#include <duye_logger.h>
#include <duye_inotify.h>
#include <duye_file_monitor.h>

namespace duye {

static const int8* DUYE_LOG_PREFIX = "duye.file.monitor";

FileMonitor::FileMonitor() : m_monitor(NULL), m_stoped(true), m_notify_fd(-1) {
    m_event_desc_map[IN_ACCESS] = "file was accessed";
    m_event_desc_map[IN_ATTRIB] = "file attributes were changed";
    m_event_desc_map[IN_CLOSE_WRITE] = "writtable file closed";
    m_event_desc_map[IN_CLOSE_NOWRITE] = "unwrittable file closed";
    m_event_desc_map[IN_CREATE] = "file was created";
    m_event_desc_map[IN_DELETE] = "file was deleted";
    m_event_desc_map[IN_DELETE_SELF] = "self was deleted";
    m_event_desc_map[IN_MODIFY] = "File was modified";
    m_event_desc_map[IN_MOVE_SELF] = "self was moved";
    m_event_desc_map[IN_MOVED_FROM] = "file was moved from X";
    m_event_desc_map[IN_MOVED_TO] = "file was moved to Y";
    m_event_desc_map[IN_OPEN] = "file was opened";
    m_event_desc_map[IN_MOVE] = "IN_MOVED_FROM|IN_MOVED_TO";
    m_event_desc_map[IN_CLOSE] = "IN_CLOSE_WRITE|IN_CLOSE_NOWRITE";
    // m_event_desc_map[IN_MORE_SELF] = "rename path";
}

FileMonitor::~FileMonitor() { stop(); }

bool FileMonitor::setMonitor(FileMonitorIf* monitor) {
	m_monitor = monitor;
	return true;
}

bool FileMonitor::includePath(const std::string& path) {
	if (!duye::FileUtil::isExist(path.c_str())) {
		DUYE_WARN("not exist, path:%s", path.c_str());
		return false;
	}

	m_in_path_set.insert(path);
	return true;
}

bool FileMonitor::includePath(const std::list<std::string>& path_list) {
	std::list<std::string>::const_iterator iter = path_list.begin();
	for (; iter != path_list.end(); ++iter) {
		if (!duye::FileUtil::isExist(iter->c_str())) {
			DUYE_WARN("not exist, path:%s", iter->c_str());
			continue;
		}		

		m_in_path_set.insert(*iter);
	}

	return true;
}

bool FileMonitor::excludePath(const std::string& path) {
	if (!duye::FileUtil::isExist(path.c_str())) {
		DUYE_WARN("not exist, path:%s", path.c_str());
		return false;
	}

	m_ex_path_set.insert(path);
	return true;
}

bool FileMonitor::excludePath(const std::list<std::string>& path_list) {
	std::list<std::string>::const_iterator iter = path_list.begin();
	for (; iter != path_list.end(); ++iter) {
		if (!duye::FileUtil::isExist(iter->c_str())) {
			DUYE_WARN("not exist, path:%s", iter->c_str());
			continue;
		}

		m_ex_path_set.insert(*iter);
	}

	return true;
}

bool FileMonitor::start() {
	if (m_monitor == NULL) {
		DUYE_ERROR("file monitor don't setting, call setMonitor to setting");
		return false;
	}

	m_notify_fd = m_inotify.init();
	if (m_notify_fd == -1) {
		DUYE_ERROR("m_inotify.init() failed");
		return false;
	}

	addWatchFile();

	startTask();

	return true;
}

bool FileMonitor::stop() {
	if (m_stoped) {
		return true;
	}

	m_stoped = true;
	uint32 timeout = 3;
	if (!m_stop_cond.wait(timeout * 1000)) {
		DUYE_ERROR("wait for FileMonitor to stoped timeout(%ds)", timeout);
		return false;
	}
	
	return true;
}

bool FileMonitor::run() {	
	DUYE_INFO("FileMonitor Thread:%u start", threadId());

	if (!m_epoll.open()) {
		DUYE_ERROR("%s", m_epoll.error());
		return false;
	}

	if (!m_epoll.addfd(m_notify_fd, EPOLLIN | EPOLLOUT)) {
		DUYE_ERROR("add fd=%d to epoll failed : %s", m_notify_fd, m_epoll.error());
		return false;
	}	


	m_stoped = false;
	int8 buffer[1024] = {0};

	while (!m_stoped) {
		Epoll::EventList event_list;
		if (!m_epoll.wait(event_list, 1000)) {
			continue;
		}

		if (event_list.empty()) {
			// DUYE_TRACE("event_list.empty");
			continue;
		}

		Epoll::EventList::iterator iter = event_list.begin();
		for (; iter != event_list.end(); ++iter) {   	
          	if (iter->fd() != m_notify_fd) {
          		continue;
          	}

            int32 length = read(iter->fd(), buffer, sizeof(buffer));
            if (length < (int32)sizeof(struct inotify_event)) {
                break;
            }

            int32 offset = 0;
            do {
            	std::list<FileEvent> event_list;
				struct inotify_event* event = (struct inotify_event*)(buffer + offset);
                if (event->len > 0) {
                	EventDescMap::iterator iter = m_event_desc_map.begin();
                	for (; iter != m_event_desc_map.end(); ++iter) {
                		if (event->mask & iter->first) {
                			printLog(iter->first, event->name, event->mask & IN_ISDIR);
                			event_list.push_back(FileEvent(iter->first, event->mask & IN_ISDIR, event->name));
                		}
                	}

                  	offset += (sizeof(struct inotify_event) + event->len);
                }  

                std::list<FileEvent>::iterator e_iter = event_list.begin();
                for (; e_iter != event_list.end(); ++e_iter) {
                	m_monitor->onFileChangeNotify(e_iter->path, e_iter->event , e_iter->is_dir);
                }                
			} while((int32)(offset + sizeof(struct inotify_event)) <= length);
		}		
	}

	m_epoll.delfd(m_notify_fd);

	m_stop_cond.broadcast();
	DUYE_INFO("FileMonitor Thread:%u exit", threadId());

	return true;
}

bool FileMonitor::addWatchFile() {
	uint32 success = 0;
	uint32 failure = 0;
	std::set<std::string>::iterator iter = m_in_path_set.begin();
	for (; iter != m_in_path_set.end(); ++iter) {
		if (m_ex_path_set.find(*iter) == m_ex_path_set.end()) {
			int32 wd = m_inotify.addWatch(iter->c_str());
			if (wd == -1) {
				DUYE_WARN("add watch failed, path:%s", iter->c_str());
				failure++;
			} else {
				success++;
			}
		}
	}

	DUYE_INFO("add file watch success=%d, failure=%d", success, failure);

	return true;
}

void FileMonitor::printLog(const uint32 event, int8* path, const bool is_dir) {
	if (m_event_desc_map.find(event) != m_event_desc_map.end()) {
		DUYE_ERROR("don't found file monitor event:%d in m_event_desc_map", event);
		return;
 	}

	std::string log;
	if (is_dir) {
		log.append("directory: ");
	} else {
		log.append("file: ");
	}

	log.append(m_event_desc_map[event] + ", path: " + path);
	DUYE_INFO("%s", log.c_str()); 
}

}
